<?php

namespace paper\tplLibs;

use paper\App;
use paper\Exception;

class Engine
{
    private $leftLimit = '';
    private $rightLimit = '';
    private $vars = [];
    private $templateContent = '';
    private $templatePath = '';
    private $cachePath = '';

    private $request;
    private $config;


    function __construct()
    {
        $left             = preg_quote("{");
        $right            = preg_quote("}");
        $this->leftLimit  = '(\<\!\-\-)?' . $left;
        $this->rightLimit = $right . '(\-\-\>)?';
        $this->request    = App::getInstance()->request;

    }

    /**
     * @param null $template
     * @throws Exception
     */
    public function view($template = null)
    {
        $this->load($template);
    }

    /**
     * @param $str
     * @throws Exception
     */
    public function template($str)
    {
        $this->load($str);
    }


    /**
     * @param $template
     * @throws Exception
     */
    public function load($template)
    {
        $template           = $this->parseTemplatePath($template);
        //        var_dump($template);
        $this->templatePath = $template;
        $this->cachePath    = ROOT . '/runtime/tpl_cache/' . md5($template) . '.php';

        if (!file_exists($this->templatePath)) {
            throw new Exception("模板不存在" . $this->templatePath);
        }

        if (!$this->checkCache()) {
            $this->templateContent = $this->loadFile($this->templatePath);
            if ($extendFile = $this->hasExtend()) {
                $baseFilePath          = $extendFile;
                $listen[$baseFilePath] = time();
                $this->parseExtend($baseFilePath);
            }
            $cache                       = $this->parseTemplate();
            $listen[$this->templatePath] = time();
            $cacheInfo                   = serialize($listen);
            $check_path                  = '<?php if(!defined(\'IN_APP\')){die(\'error!this is a cache file!\');/*' . $cacheInfo . '*/}?>';
            if (!file_put_contents($this->cachePath, $check_path . $cache)) {
                throw new Exception($this->cachePath . ":Cache write failed!" . $cache);
            };
        };
        //执行编译后的文件
        //单独放置一个函数 是为了不让控制器的变量和内容的变量冲突
        $this->loadCacheFile();
    }


    private function parseTemplatePath($template = null)
    {
        if (null == $template) {
            $template = lcfirst($this->request->controller(false)) . '/' . lcfirst($this->request->action(false));
        }
        return ROOT . '/' . APP . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $template . '.html';
    }

    private function hasExtend()
    {
        $m       = [];
        $pattern = '/' . $this->leftLimit . 'extend\s*name="(.+?)"' . $this->rightLimit . '/i';
        if (preg_match($pattern, $this->templateContent, $m)) {
            return $this->parseTemplatePath($m[2]);
        }
        return false;
    }

    private function parseVarFilter($varName, $filters)
    {
        if (!$filters) {
            return "filter({$varName})";
        }
        $default_filter = false;
        //$first = array_shift($filters);
        if ($filters[0] != 'raw') {
            $default_filter = true;
        } else {
            array_shift($filters);
        }
        foreach ($filters as $filter) {
            switch ($filter) {
                case "date":
                    $varName = 'date("Y/m/d H:i:s",' . $varName . ')';
                    break;
                default:
                    $varName = $filter . '(' . $varName . ')';
                    break;
            }
        }
        if ($default_filter) {
            $varName = "filter({$varName})";
        }
        return $varName;
    }

    private function parseVar($varName)
    {
        $exp     = explode(".", $varName);
        $num     = count($exp);
        $varName = $exp[0];

        for ($i = 1; $i < $num; $i++) {
            $varName .= '[\'' . $exp[$i] . '\']';
        }
        return '$' . $varName;
    }

    private function parseTemplate()
    {

        $str    = $this->templateContent;
        $str    = preg_replace_callback('/' . $this->leftLimit . '\$([a-zA-Z_\x7f-\xff])([^\}]*)\s*' . $this->rightLimit . '/i', function ($m) {

            $varName = $m[2] . $m[3];
            $filters = '';
            if (strpos($varName, '|') !== false) {
                $filters = explode('|', $varName);
                $varName = array_shift($filters);
            }
            $varName = $this->parseVar($varName);
            $varName = $this->parseVarFilter($varName, $filters);
            return '<?= ' . $varName . '?>';
        }, $str);
        $tagArr = [
            '/' . $this->leftLimit . '\s*\{\*(.+?)\*\}\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*\/\*\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*\*\/\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '=\s*(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\:\s*(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . 'php\s*\s\s*(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\/php' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*if\s+condition=\"(.+?)\"\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*if\s+condition=\'(.+?)\'\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*if\s*\s\s*(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*\/if\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*else\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*elseif\s(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*foreach\s(.+?)\sas\s(.+?)\s=>\s(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*foreach\s(.+?)\sas\s(.+?)\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*\/foreach\s*' . $this->rightLimit . '/i',
            '/' . $this->leftLimit . '\s*template\((.+?)\)\s*' . $this->rightLimit . '/i',
        ];
        $phpArr = [
            '',
            '<?php /* ?>',
            '<?php */ ?>',
            '<?= filter($2);?>',
            '<?= $2;?>',
            '<?php $2;?>',
            '<?php /* end php */};?>',
            '<?php if($2): ?>',
            '<?php if($2): ?>',
            '<?php if($2): ?>',
            '<?php endif; ?>',
            '<?php else: ?>',
            '<?php elseif($2): ?>',
            '<?php foreach($2 as $3 => $4): ?>',
            '<?php foreach($2 as $3):  ?>',
            '<?php endforeach; ?>',
            '<?php $this->template($2); ?>',
        ];

        $str = preg_replace($tagArr, $phpArr, $str);

        return $str;
    }


    /**
     * @param $extendFile
     * @throws Exception
     */
    private function parseExtend($extendFile)
    {
        $str     = $this->templateContent;
        $blocks  = [];
        $pattern = '/' . $this->leftLimit . 'block\s*name=\"([a-zA-Z0-9_]*)\"' . $this->rightLimit . '\s*(.*?)\s*' . $this->leftLimit . '\/block' . $this->rightLimit . '/is';
        preg_match_all($pattern, $str, $m);
        $patternCount = count($m[2]);

        for ($i = 0; $i < $patternCount; $i++) {
            $blockName          = $m[2][$i];
            $blockValue         = $m[4][$i];
            $blocks[$blockName] = $blockValue;
        }

        $baseContent = $this->loadFile($extendFile);

        $this->templateContent = preg_replace_callback($pattern, function ($m) use ($blocks) {
            $blockName = $m[2];
            return array_key_exists($blockName, $blocks) ? $blocks[$blockName] : $m[4];
        }, $baseContent);
        //var_dump($this->templateContent);
    }

    private function checkCache()
    {
        if (!file_exists($this->cachePath)) {
            return false;
        }
        // 读取缓存文件失败
        if (!$handle = @fopen($this->cachePath, "r")) {
            return false;
        }
        // 读取第一行
        preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches);
        if (!isset($matches[1])) {
            return false;
        }
        $includeFile = unserialize($matches[1]);
        if (!is_array($includeFile)) {
            return false;
        }

        // 检查模板文件是否有更新
        foreach ($includeFile as $path => $time) {
            if (!is_file($path) || filemtime($path) > $time) {
                // 模板文件如果有更新则缓存需要更新
                return false;
            }
        }
        return true;
    }

    /**
     * @param $file
     * @return bool|string
     * @throws Exception
     */
    private function loadFile($file)
    {
        if (!is_file($file)) {
            throw new Exception("文件不存在" . $file);
        }
        return file_get_contents($file);
    }

    /**
     *
     */
    private function loadCacheFile()
    {
        extract($this->vars);
        include($this->cachePath);
    }


    /**
     * @param $var
     * @param string $value
     */
    public function set($var, $value = '')
    {
        $this->vars[$var] = $value;
    }
}